﻿using System;
using System.Collections.Generic;

namespace IMMeDotNet {
	partial class Host {

		/// <summary>
		/// Invoked when a full <see cref="IncomingUsbMessage"/> has been received.
		/// </summary>
		/// <param name="message">The <see cref="IncomingUsbMessage"/> that has been received from the wireless adaptor.</param>
		private void ProcessIncomingUsbMessage(IncomingUsbMessage message) {
			switch (message.MessageClass) {
				case 0x80:
					// Computer identification.
					var computerName = new IMMeString(System.Net.Dns.GetHostName());
					var identification = new OutgoingUsbMessage(0x00, 0x01, 0x80);
					identification.Write(Error.OK);
					identification.Write((uint)Crc32.Calculate(computerName.ToByteArray())); // Not sure what this is for.
					identification.Write(computerName);
					this.Write(identification);
					break;
				case 0x00:
					if (message.ID1 == 0x01 && message.ID2 == 0x00) {

						// Add/Remove friend.
						var target = this.ReadMessageTarget(message);
						bool isAdding = message.ReadByte() != 0;
						var friendName = message.ReadString();

						// Do we recognise that contact?
						if (target != null) {

							if (isAdding) {

								// Add the friend.

								var friend = new Contact(friendName);
								target.Connection.Friends.RemoveAll(f => f.ID == friend.ID || f.Username == friend.Username);
								target.Connection.Friends.Add(friend);

								// Respond to the addition request.

								var response = message.CreateResponse();
								response.Write(target);
								response.Write(Error.OK);
								response.Write(friend.ID);
								this.Write(response);

								// Fire the event.

								this.OnFriendAdded(new FriendEventArgs(target.Connection, friend));

							} else {

								// Delete the friend.

								var friend = target.Connection.Friends.Find(f => f.Username == friendName);
								target.Connection.Friends.RemoveAll(f => f.Username == friendName);

								// Respond to the deletion request.

								var response = message.CreateResponse();
								response.Write(target);
								response.Write(Error.OK);
								response.Write(friend.ID);
								this.Write(response);
								
								// Fire the event.

								if (friend != null) this.OnFriendRemoved(new FriendEventArgs(target.Connection, friend));

							}
						}

					} else {
						Console.WriteLine(message);
					}
					break;
				case 0x01:
					if (message.ID1 == 0x01 && message.ID2 == 0x00) {

						// Log in attempt.

						var messageId = message.ReadByte();
						message.ReadByte();
						var messageKey = message.ReadUInt32();
						var username = message.ReadString();
						var password = message.ReadString();

						// Who is it?
						var contact = new Contact(username);

						// Attempt to log in (fire event).
						var authenticationArgs = new AuthenticatedContactEventArgs(contact, password, false);
						this.OnAttemptedLogIn(authenticationArgs);
						if (this.connections.Count == 255) authenticationArgs.Success = false; // Too many incoming.

						Connection newConnection = null;
						if (authenticationArgs.Success) {
							newConnection = new Connection(this, (byte)this.connections.Count, contact);
							this.connections.Add(newConnection);
						}

						// Create a response.
						var loginResponse = new OutgoingUsbMessage(0x00, 0x01, 0x01);
						loginResponse.Write(messageId);
						loginResponse.Write(newConnection == null ? (byte)0 : newConnection.ID);
						loginResponse.Write((uint)(authenticationArgs.Success ? authenticationArgs.Contact.ID : 0x00000000));
						loginResponse.Write(authenticationArgs.Success ? Error.OK : Error.IncorrectPassword);
						loginResponse.Write(messageKey);
						this.Write(loginResponse);

						// Accept the connection if log in was successful.
						if (authenticationArgs.Success) {
							this.OnLogIn(new ConnectionEventArgs(newConnection));
						}

					} else if (message.ID1 == 0x01 && message.ID2 == 0x08) {

						// Get friend list.
		
						var target = this.ReadMessageTarget(message);

						// Do we know that contact?
						if (target != null) {

							// Respond with a friend list.
							this.OnRequestContactList(new ConnectionEventArgs(target.Connection));

							// Build up the friend list response message.
							var friendList = new OutgoingUsbMessage(0x00, 0x09, 0x01);

							friendList.Write(target);
							friendList.Write((byte)0x00);

							// Number of friends.
							friendList.Write((byte)target.Connection.Friends.Count);

							// Write each friend as an ID and a username.
							foreach (var friend in target.Connection.Friends) {
								friendList.Write(friend.ID);
								friendList.Write(friend.Username);
							}

							this.Write(friendList);

						}
					} else if (message.ID1 == 0x01 && message.ID2 == 0x04) {

						// Who's Online.

						var target = this.ReadMessageTarget(message);

						// Do we know that contact?
						if (target != null) {

							// Respond with a list of "who's online".
							this.OnRequestOnlineFriendList(new ConnectionEventArgs(target.Connection));

							// Build up the "who's online" list response message.
							var onlineFriendList = new OutgoingUsbMessage(0x00, 0x05, 0x01);

							onlineFriendList.Write(target);
							onlineFriendList.Write((byte)0x00);

							var onlineContacts = target.Connection.Friends.FindAll(c => c.IsOnline);							

							// Number of friends.
							onlineFriendList.Write((byte)onlineContacts.Count);

							// Write each friend as an ID only (no username, that was exchanged earlier).
							foreach (var friend in onlineContacts) {
								onlineFriendList.Write(friend.ID);
							}

							this.Write(onlineFriendList);
						}

					} else if (message.ID1 == 0x01 && message.ID2 == 0x02) {
						
						// Disconnection.

						var target = this.ReadMessageTarget(message);

						// Do we know that contact?
						if (target != null) {

							// Acknowledge disconnection.
							var disconnectionResponse = new OutgoingUsbMessage(0x00, 0x03, 0x01);

							disconnectionResponse.Write(target);
							disconnectionResponse.Write((byte)0x00);

							this.Write(disconnectionResponse);

							// Remove the contact from our internal list.
							this.connections[target.Connection.ID] = null;

							this.OnLogOff(new ConnectionEventArgs(target.Connection));
						}

					} else {
						Console.WriteLine(message);
					}
					break;
				case 0x02:
					
					// Text Messages.

					if (message.ID1 == 0x01 && message.ID2 == 0x04) {

						// We've received a text message.

						var target = this.ReadMessageTarget(message);
						var textID = message.ReadUInt32();
						var recipientID = message.ReadUInt32();

						var text = message.ReadString();

						// Is this message deliverable to anyone?
						// Do we know that contact?
						if (target != null) {
		
							// Does the recipient know the sender?
							Contact recipient = null;
							foreach (var potentialRecipient in target.Connection.Friends) {
								if (potentialRecipient.ID == recipientID) {
									recipient = potentialRecipient;
									break;
								}
							}

							// Acknowledge the message.
							var textAcknowledgement = message.CreateResponse();
							textAcknowledgement.Write(target);
							textAcknowledgement.Write(textID);
							textAcknowledgement.Write(recipient == null ? Error.NotInFriendList : Error.OK);

							this.Write(textAcknowledgement);

							// Fire an event about the received message.
							if (recipient != null) {
								var textMessage = new MessageEventArgs(target.Connection, new Message(target.Connection.Contact, recipient, text));
								this.OnMessageReceived(textMessage);
							}

						}
					} else if (message.ID1 == 0x00 && message.ID2 == 0x07) {
						
						// Acknowledgement for a message we've sent.

						var target = this.ReadMessageTarget(message);
						var textID = message.ReadUInt32();
						var recipientID = message.ReadUInt32();
						message.ReadByte();

						// Is this a connection we know?
						if (target != null) {
							// Is this a message we recognise?
							var acknowledgedMessage = target.Connection.GetMessageAcknowledged(textID);
							if (acknowledgedMessage != null) {
								this.OnMessageSent(new MessageEventArgs(target.Connection, acknowledgedMessage));
							}
						}
						
					} else if (message.ID1 == 0x01 && message.ID2 == 0x08) {

						// Ending a chat.

						var target = this.ReadMessageTarget(message);
						var textID = message.ReadUInt32();
						message.ReadByte(); // ?

						// We're trying to end a chat.
						if (target != null) {

							// Remove the text message ID.

							var friend = target.Connection.GetContactFromLastMessageID(textID);

							// Respond to the chat end event.

							var response = message.CreateResponse();
							response.Write(target);
							response.Write(textID);
							response.Write(Error.OK);

							this.Write(response);

							// Fire the event.

							this.OnDiscussionEnded(new FriendEventArgs(target.Connection, friend));

						}
					} else {
						Console.WriteLine(message);
					}
					break;
				default:
					Console.WriteLine(message);
					break;
			}
		}

		private IdentifiedMessageTarget ReadMessageTarget(IncomingUsbMessage message) {
			var sourceTarget = message.ReadTarget();
			var result = new IdentifiedMessageTarget(sourceTarget.MessageID, sourceTarget.ConnectionID, sourceTarget.ContactID);
			if ((result.Connection = this.GetConnection(result.ConnectionID)) == null || (result.Contact = result.Connection.Contact).ID != result.ContactID) {
				result = null;
			}
			return result;
		}

	}
}
